#!/usr/bin/env php
<?php
$SOAR_DIR=getenv("SOAR_DIR");
if(empty($SOAR_DIR)) $SOAR_DIR="/opt/soar";
$SOAR_DIR="../../../../..";
include "$SOAR_DIR/common/php/soar/soar.inc";

class cnfg
{/*{{{*/
    static $ERRLOG=array(
            'LOCATION'=>"_log",
            'TRACKING'=>"Y"
            );

    static $ACCESS=array(
            'PUSH_PORT'=>59990,
            'CALL_PORT'=>59991
            );
}/*}}}*/

class worker
{/*{{{*/
    public static $listen_slots=array();    // 'push'=> 'call'=>
    public static $ep=null;

    private static $online=array();

    private function SVC_INPUT($request)
    {/*{{{*/
        $curr=0;
        $from=para::get_item($request, $curr);
        $talk=para::get_item($request, $curr);
        if(!is_null($from) && !is_null($talk))
        {
            errlog::add("%s: %s %s", __METHOD__, $from, $talk);
            $buf='';
            para::put_item($buf, $from, $talk);
            foreach(self::$online as $slot=>$info)
            {
                $res=self::$ep->send($slot, $buf);
                if($res == false)
                {
                    errlog::add("%s: push to %d fail, BAD",
                            __METHOD__, $slot);
                }
            }
        }
        else
        {
            errlog::add("%s: paras(%s) invalid",
                    __METHOD__, $request);
        }

        $response='';
        para::put_item($response, 'OK');
        return $response;
    }/*}}}*/
    
    private function callsvc($request)
    {/*{{{*/
        $response='';
        $curr=0;
        $svc=para::get_item($request, $curr);
        if(empty($svc) || strlen($svc) > misc::NAME_SIZE)
        {
            errlog::add("%s: svc(%s) invalid",
                    __METHOD__,
                    empty($svc)?'null':$svc);
            para::put_item($response, 'SVC_INVALID'); 
        }

        switch($svc)
        {
            case 'INPUT':
                $response=self::SVC_INPUT(substr($request, $curr));
                break;
            default:
                errlog::add("%s: func(%s) not found",
                        __METHOD__, $svc);
                para::put_item($response, 'SVC_NOT_FOUND');
                break;
        }

        return $response;
    }/*}}}*/

    private function conn($active)
    {/*{{{*/
        if($active['slot_id'] == self::$listen_slots['push'])
        {
            errlog::add("%s: new push", __METHOD__);
            $new_slot=self::$ep->add($active['new_sock']);
            if($new_slot === false)
            {
                errlog::add("%s: ep::add fail", __METHOD__);
                socket_close($active['new_sock']);
                return false;
            }
            self::$ep->set_wrap($new_slot);
            self::$online[$new_slot]=array();
        }
        else    // call
        {
            errlog::add("%s: new call", __METHOD__);
            $timeout=10;
            if(sock::recv($active['new_sock'], $request, $timeout) == true)
            {
                $response=self::callsvc($request);
                if(sock::send($active['new_sock'], $response,
                            $timeout) == true)
                {
                }
                else
                {
                    errlog::add("%s: send to call sock(%d) fail",
                            __METHOD__, $active['new_sock']);
                }
            }
            else
            {
                errlog::add("%s: recv from call sock(%d) fail",
                        __METHOD__, $active['new_sock']);
            }
            socket_close($active['new_sock']);
        }
        return true;
    }/*}}}*/

    private function disconn($active)
    {/*{{{*/
        unset(self::$online[$active['slot_id']]);
        errlog::add("%s: online(%s) %s, error:%d",
                __METHOD__, $active['addr'],
                ($active['type']==ep::ABORT)?"abort":"timeout",
                $active['error']);
        return true;
    }/*}}}*/

    public function start()
    {/*{{{*/
        while(true)
        {
            $active_list=self::$ep->poll();
            if($active_list === false)
            {
                errlog::add("%s: ep::poll fail", __METHOD__);
            }
            else
            {
                foreach($active_list as $active)
                {
                    //print_r($active);
                    switch($active['type'])
                    {
                        case ep::CONN:
                            self::conn($active);
                            break;
                        case ep::RECV:
                            errlog::add("%s: type:RECV impossible",
                                    __METHOD__);
                            break;
                        case ep::ABORT:
                        case ep::TIMEOUT:
                            self::disconn($active);
                            break;
                    }
                }
            }
        }
    }/*}}}*/
}/*}}}*/

function start($daemon)
{/*{{{*/
    if($daemon)
    {
        $pid=pcntl_fork();
        if($pid == -1)
        {
            printf("%s: daemonize fail", __METHOD__);
            return false;
        }
        if($pid > 0)
        {
            return true;
        }
        // errlog
        if(errlog::init(basename(__FILE__), cnfg::$ERRLOG['LOCATION'])
                == false)
        {
            printf("%s: init errlog fail", __METHOD__);
            return false;
        }
    }

    worker::$ep=new ep();

    $sock=sock::listen(cnfg::$ACCESS['PUSH_PORT']);
    if($sock === false)
    {
        errlog::add("%s: open push_port(%d) fail",
                __METHOD__, cnfg::$ACCESS['PUSH_PORT']);
        return false;
    }
    $slot_id=worker::$ep->add_listen($sock);
    if($slot_id === false)
    {
        errlog::add("%s: ep::add_listen(push) fail", __METHOD__);
        return false;
    }
    worker::$listen_slots['push']=$slot_id;
    printf("open push port:%d slot:%d\n",
            cnfg::$ACCESS['PUSH_PORT'], $slot_id);
    errlog::add("%s: open push port %d",
            __METHOD__, cnfg::$ACCESS['PUSH_PORT']);

    $sock=sock::listen(cnfg::$ACCESS['CALL_PORT']);
    if($sock === false)
    {
        printf("%s: open call_port(%d) fail",
                __METHOD__, cnfg::$ACCESS['CALL_PORT']);
        return false;
    }
    $slot_id=worker::$ep->add_listen($sock);
    if($slot_id === false)
    {
        errlog::add("%s: ep::add_listen(call) fail", __METHOD__);
        return false;
    }
    worker::$listen_slots['call']=$slot_id;
    printf("open call port:%d slot:%d\n",
            cnfg::$ACCESS['CALL_PORT'], $slot_id);
    errlog::add("%s: open call port %d",
            __METHOD__, cnfg::$ACCESS['CALL_PORT']);

    return worker::start();
}/*}}}*/

function main($argv)
{/*{{{*/
    $daemon=false;
    if(isset($argv[1]))
    {
        if($argv[1] == "-d" || $argv[1] == "--daemon")
           $daemon=true; 
    }
    if(start($daemon) == false)
        return 1;
    return 0;
}/*}}}*/

exit(main($argv));
?>
